/*
 * Copyright 2004-2006, Thorbjrn Lindeijer <thorbjorn@lindeijer.nl>
 * Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
 *
 * This file is part of libtiled-java.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package tiled.core;

import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * A layer containing {@link MapObject map objects}.
 */
public class ObjectGroup extends MapLayer {
	private LinkedList<MapObject>	objects	= new LinkedList<MapObject>();

	/**
	 * Default constructor.
	 */
	public ObjectGroup() {
	}

	/**
	 * @param map
	 *            the map this object group is part of
	 */
	public ObjectGroup(Map map) {
		super(map);
	}

	/**
	 * Creates an object group that is part of the given map and has the given origin.
	 * 
	 * @param map
	 *            the map this object group is part of
	 * @param origX
	 *            the x origin of this layer
	 * @param origY
	 *            the y origin of this layer
	 */
	public ObjectGroup(Map map, int origX, int origY) {
		super(map);
		this.setBounds(new Rectangle(origX, origY, 0, 0));
	}

	/**
	 * Creates an object group with a given area. The size of area is irrelevant, just its origin.
	 * 
	 * @param area
	 *            the area of the object group
	 */
	public ObjectGroup(Rectangle area) {
		super(area);
	}

	/**
	 * @see MapLayer#rotate(int)
	 */
	@Override
	public void rotate(int angle) {
		// TODO: Implement rotating an object group
	}

	/**
	 * @see MapLayer#mirror(int)
	 */
	@Override
	public void mirror(int dir) {
		// TODO: Implement mirroring an object group
	}

	@Override
	public void mergeOnto(MapLayer other) {
		// TODO: Implement merging with another object group
	}

	@Override
	public void maskedMergeOnto(MapLayer other, Area mask) {
		// TODO: Figure out what object group should do with this method
	}

	@Override
	public void copyFrom(MapLayer other) {
		// TODO: Implement copying from another object group (same as merging)
	}

	@Override
	public void maskedCopyFrom(MapLayer other, Area mask) {
		// TODO: Figure out what object group should do with this method
	}

	@Override
	public void copyTo(MapLayer other) {
		// TODO: Implement copying to another object group (same as merging)
	}

	/**
	 * @see MapLayer#resize(int,int,int,int)
	 */
	@Override
	public void resize(int width, int height, int dx, int dy) {
		// TODO: Translate contained objects by the change of origin
	}

	@Override
	public boolean isEmpty() {
		return this.objects.isEmpty();
	}

	@Override
	public Object clone() throws CloneNotSupportedException {
		ObjectGroup clone = (ObjectGroup) super.clone();
		clone.objects = new LinkedList<MapObject>();
		for (MapObject object : this.objects) {
			final MapObject objectClone = (MapObject) object.clone();
			clone.objects.add(objectClone);
			objectClone.setObjectGroup(clone);
		}
		return clone;
	}

	/**
	 * @deprecated
	 */
	@Deprecated
	@Override
	public MapLayer createDiff(MapLayer ml) {
		return null;
	}

	public void addObject(MapObject o) {
		this.objects.add(o);
		o.setObjectGroup(this);
	}

	public void removeObject(MapObject o) {
		this.objects.remove(o);
		o.setObjectGroup(null);
	}

	public Iterator<MapObject> getObjects() {
		return this.objects.iterator();
	}

	public MapObject getObjectAt(int x, int y) {
		for (MapObject obj : this.objects) {
			// Attempt to get an object bordering the point that has no width
			if ((obj.getWidth() == 0) && ((obj.getX() + this.bounds.x) == x)) {
				return obj;
			}

			// Attempt to get an object bordering the point that has no height
			if ((obj.getHeight() == 0) && ((obj.getY() + this.bounds.y) == y)) {
				return obj;
			}

			Rectangle rect = new Rectangle(obj.getX() + (this.bounds.x * this.myMap.getTileWidth()), obj.getY() + (this.bounds.y * this.myMap.getTileHeight()),
					obj.getWidth(), obj.getHeight());
			if (rect.contains(x, y)) {
				return obj;
			}
		}
		return null;
	}

	// This method will work at any zoom level, provided you provide the correct zoom factor. It also adds a one pixel buffer (that doesn't change with zoom).
	public MapObject getObjectNear(int x, int y, double zoom) {
		Rectangle2D mouse = new Rectangle2D.Double(x - zoom - 1, y - zoom - 1, (2 * zoom) + 1, (2 * zoom) + 1);
		Shape shape;

		for (MapObject obj : this.objects) {
			if ((obj.getWidth() == 0) && (obj.getHeight() == 0)) {
				shape = new Ellipse2D.Double(obj.getX() * zoom, obj.getY() * zoom, 10 * zoom, 10 * zoom);
			} else {
				shape = new Rectangle2D.Double(obj.getX() + (this.bounds.x * this.myMap.getTileWidth()), obj.getY()
						+ (this.bounds.y * this.myMap.getTileHeight()), obj.getWidth() > 0 ? obj.getWidth() : zoom, obj.getHeight() > 0 ? obj.getHeight()
						: zoom);
			}

			if (shape.intersects(mouse)) {
				return obj;
			}
		}

		return null;
	}
}
